02. R 프로그래밍 기초¶
2.1 산술 연산자¶
계산용 연산자¶
R은 계산기처럼 사칙연산과 거듭제곱을 바로 실행할 수 있습니다.
+(더하기),-(빼기),*(곱하기),/(나누기),^(자승)
연산자를 활용한 예제 — 화씨온도가 100도라면 섭씨온도는?
나머지·몫 연산자¶
%%(나머지)와 %/%(몫)는 정수 관련 계산에서 자주 쓰이는 연산자입니다. 예를 들어 어떤 숫자가 짝수인지 홀수인지 판별할 때, "2로 나눈 나머지가 0인가?"를 물으면 됩니다.
5 %% 2 # 나머지
#> [1] 1
5 %/% 2 # 몫
#> [1] 2
# 짝수·홀수 판별 예시
10 %% 2 == 0
#> [1] TRUE
7 %% 2 == 0
#> [1] FALSE
2.2 논리 연산자¶
논리 연산자는 조건이 참(TRUE)인지 거짓(FALSE)인지를 따질 때 사용합니다.
5 == 5 # 같다
#> [1] TRUE
5 != 5 # 같지 않다
#> [1] FALSE
5 > 5
#> [1] FALSE
5 >= 5
#> [1] TRUE
3 < 5
#> [1] TRUE
6 <= 5
#> [1] FALSE
!TRUE # NOT x
#> [1] FALSE
TRUE | FALSE # x OR y
#> [1] TRUE
TRUE & FALSE # x AND y
#> [1] FALSE
xor(TRUE, FALSE) # 배타적 OR (둘 중 하나만 TRUE일 때 TRUE)
#> [1] TRUE
isTRUE(TRUE) # x가 TRUE인지 확인
#> [1] TRUE
&·|vs&&·||
&와|는 벡터의 각 요소끼리 비교하는 연산자이고,&&와||는 좌우 값을 각각 하나의 논리값으로만 취급해 왼쪽부터 순서대로 평가하는(단락 평가) 연산자입니다.if(),while()조건문에는 길이가 1인 논리값이 와야 하므로&&,||를 사용합니다.버전별로 강화된 시점이 다릅니다:
if()/while()조건에 길이 2 이상인 벡터를 넣으면 R 4.2.0부터 아예 오류가 발생합니다(이전에는 경고만 내고 첫 번째 값만 사용했습니다).&&/||는 한 단계를 더 거쳤는데, R 4.2.0에서 먼저 경고가 도입되었고, R 4.3.0부터 그 경고가 오류로 격상되었습니다. 실수로 벡터를 조건문에 넘기는 흔한 버그를 미리 잡아주는 변화입니다.x <- c(TRUE, FALSE) if (x) print("실행됨") #> Error in if (x) print("실행됨") : #> the condition has length > 1 y <- c(TRUE, TRUE) x && y #> Error in x && y : 'length = 2' in coercion to 'logical(1)'따라서 조건문에는 항상 길이가 1인 논리값만 들어가도록
any()나all()로 벡터를 요약한 뒤 사용하는 것이 안전합니다.
2.3 산술 함수¶
앞서 살펴본 연산자 외에도 R은 합계·평균·반올림·로그·삼각함수 등 방대한 내장 산술·통계 함수를 제공합니다.
2.4 객체(변수)의 할당과 삭제¶
객체 만들기¶
변수는 일종의 데이터를 담고 있는 상자입니다. 이 상자에는 숫자나 문자 등을 넣어 보관하거나 필요할 때 빼서 사용할 수 있고, 상자 안의 내용물을 바꿀 수도 있습니다.
변수명은 항상 왼쪽에 쓰며, <- 기호를 사용하여 변수라는 상자 안에 데이터를 넣을 수 있습니다(예: a <- 2). <- 대신 =를 사용해도 되지만, R 커뮤니티(구글 R 스타일 가이드, tidyverse 스타일 가이드 등)에서는 대입에는 <-, 함수 인자 지정에는 =를 쓰는 관례를 권장합니다. RStudio에서는 Alt + -(맥은 Option + -) 단축키로 <-를 빠르게 입력할 수 있습니다.
변수명은 영문과 숫자를 함께 사용할 수 있으나 시작 글자는 반드시 문자(또는 숫자로 이어지지 않는 마침표)여야 하며, if·for처럼 R이 이미 문법적으로 사용하는 예약어는 변수명으로 쓸 수 없습니다.
# 3이라는 숫자를 변수 x에 담기
x <- 3
# 5라는 숫자를 변수 y에 담기
y <- 5
# 변수 x에 담긴 값을 출력
x
#> [1] 3
# print() 함수를 사용하여 출력 가능
print(y)
#> [1] 5
# 변수 x에 담긴 값과 변수 y에 담긴 값 계산
x * y # 3 * 5
#> [1] 15
x + y # 3 + 5
#> [1] 8
# x/y의 결과 값을 변수 z에 담기
z <- x / y
z
#> [1] 0.6
객체 목록 보기¶
ls()는 현재 세션(작업공간)에 만들어져 있는 객체 목록을 알파벳순으로 보여줍니다.
객체 지우기¶
rm()으로 더 이상 필요 없는 객체를 삭제할 수 있습니다.
작업공간에 있는 객체를 한 번에 모두 지우고 싶다면 rm(list = ls())를 사용합니다.
2.5 기본 데이터 타입¶
기본 데이터 타입으로 숫자(numeric), 문자(character), 논리(logical), 날짜(date), 결측치(missing) 등이 있습니다.
# 숫자 데이터
my_age <- 21
# 문자 데이터 - 문자는 따옴표(" 또는 ')로 둘러쌉니다.
my_name <- "영희"
# 논리 데이터 - TRUE, FALSE
is_student <- TRUE
# 날짜 데이터 (자세한 생성·연산 방법은 10장 날짜·시간 함수 참고)
my_birthday <- as.Date("2007-08-02")
# 결측치(missing data) - 논리 데이터의 일종
my_missing <- NA
문자는 따옴표로 둘러싸는데, 문자열 안에 따옴표가 포함되어야 한다면 \를 앞에 붙여 이스케이프 처리합니다.
데이터 타입을 확인하는 함수는 class()이고, 특정 타입인지 참/거짓으로 확인하려면 is.xxx() 계열 함수를, 다른 타입으로 바꾸려면 as.xxx() 계열 함수를 사용합니다.
class(my_age)
#> [1] "numeric"
class(my_name)
#> [1] "character"
is.numeric(my_age)
#> [1] TRUE
is.character(my_name)
#> [1] TRUE
is.logical(is_student)
#> [1] TRUE
is.na(my_missing)
#> [1] TRUE
# 숫자가 따옴표에 싸여 문자로 인식된 경우, 형변환 함수로 되돌릴 수 있습니다.
as.numeric("21")
#> [1] 21
숫자(실수·정수)¶
R에서 숫자는 기본적으로 실수(double)로 취급되며, 정수임을 명시하려면 숫자 뒤에 L을 붙입니다. 큰 수나 아주 작은 수는 지수 표기(예: 1e+05)로 표시됩니다.
100000 # 1e+05 형태로 표시됨
#> [1] 1e+05
150000000 # 1.5e+08 형태로 표시됨
#> [1] 1.5e+08
5e+3 # 5000
#> [1] 5000
5e-3 # 0.005
#> [1] 0.005
# numeric 실수
x <- 21
is.numeric(x)
#> [1] TRUE
# 정수
y <- 5L
is.integer(y)
#> [1] TRUE
typeof(5) # 부동소수점 실수 double
#> [1] "double"
typeof(5L) # 정수 integer
#> [1] "integer"
typeof(5i) # 복소수 complex
#> [1] "complex"
# 정수와 실수가 같이 연산되면 결과값은 실수
4L * 2.8
#> [1] 11.2
논리¶
R 내부에서 TRUE는 1로, FALSE는 0으로 처리됩니다. 이 성질은 조건을 만족하는 개수를 셀 때 유용하게 쓰입니다.
# 60점 이상인 학생 수 세기
scores <- c(85, 40, 90, 55, 70)
scores >= 60 # 조건을 만족하는지 여부 (논리 벡터)
#> [1] TRUE FALSE TRUE FALSE TRUE
sum(scores >= 60) # TRUE는 1로 계산되므로 합계가 곧 개수가 됨
#> [1] 3
NULL과 NA의 차이¶
데이터에 값이 있어야 할 자리인데 값을 몰라서 비워둔 경우와, 그 자리 자체가 아예 존재하지 않는 경우를 R은 어떻게 구별할까요? 예를 들어 설문조사에서 "이 문항에 응답하지 않음"과 "이 문항 자체가 없음"은 분명히 다른 상황입니다. R은 이 두 가지를 각각 NA와 NULL이라는 별개의 값으로 구분합니다.
NA(Not Available)는 길이 1의 결측값 표시자이며 논리형(logical)을 기본 타입으로 갖는 객체이고, NULL은 길이 0의 빈 객체를 나타내는 R의 고유한 특수 값입니다. 결측치 여부를 검사할 때는 is.na()를, 객체가 비어 있는지(정의되지 않았는지) 검사할 때는 is.null()을 사용합니다.
벡터에 두 값을 각각 넣어 비교해 보면 차이가 분명해집니다.
# NA: "값은 있어야 하는데 모른다" - 길이 1을 차지하는 자리
x <- c(1, NA, 3)
length(x)
#> [1] 3
# NULL: "아무것도 없다" - 벡터에 합쳐지면 자리조차 남기지 않고 사라짐
y <- c(1, NULL, 3)
length(y)
#> [1] 2
class()와 is.xxx() 함수로 확인해도 둘은 서로 다른 존재임을 알 수 있습니다.
class(NA)
#> [1] "logical"
class(NULL)
#> [1] "NULL"
is.na(NA)
#> [1] TRUE
is.null(NULL)
#> [1] TRUE
is.null(NA)
#> [1] FALSE
NA는 "값이 있어야 할 자리에 값을 모른다"는 결측치 표시로, 다른 값들과 마찬가지로 벡터 안에서 자리(길이 1)를 차지합니다. 반면 NULL은 "아무 것도 없음, 정의되지 않음" 자체를 뜻하는 특수한 객체로, 길이가 0이며 다른 값들과 합쳐지면 그냥 사라집니다. 이 성질 때문에 NULL은 리스트나 객체에서 특정 요소를 통째로 없애는 용도로도 자주 쓰입니다.
같은 원리로 객체 전체에 NULL을 대입하면 그 객체의 내용이 완전히 비워집니다.
2.6 내장 상수¶
# 영어 알파벳 대문자 26개
LETTERS
#> [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S"
#> [20] "T" "U" "V" "W" "X" "Y" "Z"
# 영어 알파벳 소문자 26개
letters
#> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s"
#> [20] "t" "u" "v" "w" "x" "y" "z"
# 영어 월 이름 약어
month.abb
#> [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
# 영어 월 이름
month.name
#> [1] "January" "February" "March" "April" "May" "June"
#> [7] "July" "August" "September" "October" "November" "December"
# 원주율 : 원의 지름에 대한 둘레의 비율
pi
#> [1] 3.141593
주의:
TRUE/FALSE와 달리, 이 상수들과T/F(TRUE·FALSE의 축약형)는 R의 진짜 예약어가 아니라 base 패키지가 미리 만들어 둔 일반 객체입니다. 따라서 실수로 같은 이름의 변수를 만들면 원래 값이 가려질 수 있습니다. 특히T <- FALSE처럼 덮어쓰면 이후 코드에서if (T)같은 조건이 예상과 다르게 동작하므로,T·F보다는 항상TRUE·FALSE를 온전히 써주는 것이 안전합니다.
2.7 예약어¶
R 예약어는 R에서 특별한 의미를 가지고 있는 단어입니다. 따라서 변수명이나 사용자가 만드는 함수 이름으로 사용하지 않는 것이 좋습니다.
예약어: if, else, repeat, while, function, for, in, next, break, TRUE, FALSE, NULL, Inf, NaN, NA, NA_integer_, NA_real_, NA_complex_, NA_character_, ..., ..1, ..2 등
R의 예약어 전체 목록은 다음과 같이 확인할 수 있습니다.
2.8 파이프 연산자 |> (R 4.1+)¶
데이터를 여러 단계로 가공할 때, 함수를 안쪽부터 바깥쪽으로 겹겹이 감싸서 쓰면(중첩 함수 호출) 코드를 안에서부터 거꾸로 읽어야 해서 처리 순서를 파악하기 어렵습니다.
파이프 연산자 |>는 왼쪽의 결과를 오른쪽 함수의 (기본적으로) 첫 번째 인자로 전달해, 위 계산을 "실행되는 순서 그대로" 왼쪽에서 오른쪽으로 이어 쓸 수 있게 해줍니다. 왼쪽 |> 오른쪽함수(인자, ...)는 오른쪽함수(왼쪽, 인자, ...)와 같습니다.
두 코드는 완전히 동일한 계산을 수행하지만, 파이프를 사용한 쪽이 "합을 구하고 → 제곱근을 취하고 → 반올림한다"는 작업 순서를 눈으로 따라가기 훨씬 쉽습니다.
왼쪽 값을 첫 번째 자리가 아닌 다른 인자 자리에 넣어야 할 때는 R 4.2.0부터 도입된 밑줄(_) 플레이스홀더를 이름 있는 인자에 사용합니다. (반드시 인자이름 = _ 형태로, 오른쪽 표현식에 한 번만 등장해야 합니다.)
R 4.3.0부터는 _ 플레이스홀더를 $, [[ 같은 추출 연산자 바로 앞에도 쓸 수 있게 확장되었습니다.
|>는 R 4.1.0에서 도입된 문법적 순전파(forward-pipe) 연산자로, 별도의 패키지 설치 없이 base R만으로 동작합니다. x |> f(y)는 파싱 단계에서 f(x, y)로 치환되며, _ 플레이스홀더로 지정한 경우를 제외하면 왼쪽 값은 항상 오른쪽 함수 호출의 첫 번째 위치 인자로 삽입됩니다.
네이티브 파이프 vs magrittr %>%¶
R을 배우다 보면 tidyverse 계열 코드에서 %>%(magrittr 파이프)를 자주 마주치게 됩니다. 이 매뉴얼은 Base R 범위(tidyverse 미포함)를 다루므로 %>%를 본격적으로 설명하지는 않지만, 둘의 차이를 알아두면 다른 사람의 코드를 읽을 때 도움이 됩니다.
| 구분 | |> (네이티브 파이프) |
%>% (magrittr 파이프) |
|---|---|---|
| 제공처 | base R (R 4.1.0+, 패키지 불필요) | magrittr 패키지 (tidyverse에 포함) |
| 플레이스홀더 | _ (R 4.2.0+, 이름 있는 인자에 한 번만) |
. (여러 번, 더 자유롭게 사용 가능) |
| 성능 | 문법 치환이라 사실상 오버헤드 없음 | 함수 호출 오버헤드가 있었으나 최신 버전에서는 미미함 |
| 이 매뉴얼에서의 위치 | 기본 권장 | tidyverse 매뉴얼(별도 문서)에서 다룰 예정 |
%>%의 . 플레이스홀더는 _보다 제약이 적어(이름 없는 인자에도, 여러 번도 사용 가능) 더 유연하지만, 그만큼 별도 패키지가 필요합니다. Base R만으로 작업한다면 |>를 기본으로 사용하고, tidyverse 코드를 다룰 때만 %>%의 존재를 참고하시면 됩니다.
2.9 람다(익명 함수) \(x) (R 4.1+)¶
sapply(), lapply() 같은 반복·함수형 함수(8장 참고)에 그 자리에서만 한 번 쓸 아주 짧은 함수를 넘길 때가 많습니다. 이런 함수마다 매번 function(x) ...라고 풀어 쓰면 코드가 길어집니다.
R 4.1.0부터는 function(x)를 \(x)로 줄여 쓸 수 있습니다. \(인자, ...) 본문은 익명 함수(anonymous function, 람다) 표기법으로, function(인자, ...) 본문과 동일하게 파싱됩니다.
\(x)는 새로운 함수 종류가 아니라 function(x)의 문법적 축약형일 뿐이며, 둘은 완전히 같은 방식으로 동작합니다. 인자가 여러 개면 \(x, y) x + y처럼 쓰고, 함수 본문이 여러 줄이면 \(x) { ... }처럼 중괄호로 감쌉니다.
sapply(1:5, function(x) x^2) # 기존 방식
#> [1] 1 4 9 16 25
sapply(1:5, \(x) x^2) # 람다(익명 함수) 축약 표기
#> [1] 1 4 9 16 25
파이프와 함께 쓰면 "1부터 5까지 각각 제곱한다"는 흐름을 한 줄로 자연스럽게 표현할 수 있습니다.